import sublime
import sublime_plugin


def get_tab_size(view):
    return int(view.settings().get('tab_size', 8))


def normed_indentation_pt(view, sel, non_space=False):
    """
    Calculates tab normed `visual` position of sel.begin() relative "
    to start of line

    \n\t\t\t    => normed_indentation_pt => 12
    \n  \t\t\t  => normed_indentation_pt => 12

    Different amount of characters, same visual indentation.
    """

    tab_size = get_tab_size(view)
    pos = 0
    ln = view.line(sel)

    for pt in range(ln.begin(), ln.end() if non_space else sel.begin()):
        ch = view.substr(pt)

        if ch == '\t':
            pos += tab_size - (pos % tab_size)

        elif ch.isspace():
            pos += 1

        elif non_space:
            break
        else:
            pos += 1

    return pos


def compress_column(column):
    # "SS\T"
    if all(c.isspace() for c in column):
        column = '\t'

    # "CCSS"
    elif column[-1] == ' ':
        while column and column[-1] == ' ':
            column.pop()
        column.append('\t')

    # "CC\T"
    return column


def line_and_normed_pt(view, pt):
    return (view.rowcol(pt)[0], normed_indentation_pt(view, sublime.Region(pt)))


def pt_from_line_and_normed_pt(view, p):
    ln, pt = p
    i = start_pt = view.text_point(ln, 0)
    tab_size = get_tab_size(view)

    pos = 0

    for i in range(start_pt, start_pt + pt):
        ch = view.substr(i)

        if ch == '\t':
            pos += tab_size - (pos % tab_size)
        else:
            pos += 1

        i += 1
        if pos == pt:
            break

    return i


def save_selections(view, selections=None):
    return [[line_and_normed_pt(view, p) for p in (sel.a, sel.b)] for sel in selections or view.sel()]


def region_from_stored_selection(view, stored):
    return sublime.Region(*[pt_from_line_and_normed_pt(view, p) for p in stored])


def restore_selections(view, lines_and_pts):
    view.sel().clear()

    for stored in lines_and_pts:
        view.sel().add(region_from_stored_selection(view, stored))


def unexpand(the_string, tab_size, first_line_offset=0, only_leading=True):
    lines = the_string.split('\n')
    compressed = []

    for li, line in enumerate(lines):
        pos = 0

        if not li:
            pos += first_line_offset

        rebuilt_line = []
        column = []

        for i, char in enumerate(line):
            if only_leading and not char.isspace():
                column.extend(list(line[i:]))
                break

            column.append(char)
            pos += 1

            if char == '\t':
                pos += tab_size - (pos % tab_size)

            if pos % tab_size == 0:
                rebuilt_line.extend(compress_column(column))
                column = []

        rebuilt_line.extend(column)
        compressed.append(''.join(rebuilt_line))

    return '\n'.join(compressed)


class TabCommand(sublime_plugin.TextCommand):
    translate = False

    def run(self, edit, set_translate_tabs=False, whole_buffer=True, **kw):
        view = self.view

        if set_translate_tabs or not self.translate:
            view.settings().set('translate_tabs_to_spaces', self.translate)

        if whole_buffer or not view.has_non_empty_selection_region():
            self.operation_regions = [sublime.Region(0, view.size())]
        else:
            self.operation_regions = view.sel()

        sels = save_selections(view)
        visible, = save_selections(view, [view.visible_region()])
        self.do(edit, **kw)
        restore_selections(view, sels)
        visible = region_from_stored_selection(view, visible)
        view.show(visible, False)
        view.run_command("scroll_lines", {"amount": 1.0})


class ExpandTabs(TabCommand):
    translate = True

    def do(self, edit, **kw):
        view = self.view
        tab_size = get_tab_size(view)

        for sel in self.operation_regions:
            sel = view.line(sel)  # TODO: expand tabs with non regular offsets
            view.replace(edit, sel, view.substr(sel).expandtabs(tab_size))


class UnexpandTabs(TabCommand):
    def do(self, edit, only_leading=True, **kw):
        view = self.view
        tab_size = get_tab_size(view)

        for sel in self.operation_regions:
            the_string = view.substr(sel)
            first_line_off_set = normed_indentation_pt(view, sel) % tab_size

            compressed = unexpand(the_string, tab_size, first_line_off_set, only_leading=only_leading)

            view.replace(edit, sel, compressed)
